package spitfire.ksim.algorithm;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
import java.lang.Math;

import spitfire.ksim.node.KSensor;
import spitfire.ksim.node.KSensorNode;
import TJavaLib.TString;

public class TimeWindowAlgorithm {
									//Vergleichswerte für den Messzeitraum 
	static private long[] referenceValue = {
									24*3600*1000L, 		//24h = 1 Day
									1*3600*1000L,  		//1h
									12*3600*1000L, 		//12h
									7*24*3600*1000L, 	//7*24 h = 1 Week
									1800000L,  			//0,5h = 30 min
									24*30*3600*1000L 	//30*24h = 30 Days	
								  };
									//Passende Timewindow in der gleichen Reihenfolge zu den Vergleichswerten
	static private long[] timeWindowValues = {
									1*3600*1000L, 		//1h
									300000L,			//5min
									1800000L,			//30min
									21600000L,			//6h		(Beispiel: Messzeitraum ist eine Woche --> Angelehnt an das Verhältnis 1/30 (1Tag bei 30 Tagen Messzeit) 28*pro Woche -> 4 pro Tag --> alle 6 h
									60000L,				//1min
									24*3600*1000L, 		//24h									
									};
									//Bedeutet jetzt:
									//Bei einem Messzeitraum von 24h (Vergleichswert) wird ein TimeWindow von 1h genommen
	
	static private long bestTimeWindow;
	static private long min;
	static long difference;
	static private long[] absoluteValue = new long[6];
	static private long fitValue;
	
	
			/**
		 * Daten von dem Knoten nehmen -> die kleinste Zeitspanne von den vorhandenen Sensoren des Knoten rausfinden
		 * -> und dann anhand dieses Messzeitraums das passende TW ermitteln
		 */
	
	public static long calculateTimeWindow(KSensorNode sNode){
		long first;
		long last;
		long difference = 0;
		
		
		//Holt sich die Sensorliste (Welche Sensoren im Knoten sind
		for(int i=0; i < sNode.getSensorList().size(); i++){
								
								//Test-Output
								System.out.println("Sensor" + i + ": " + sNode.getSensorList().get(i).getSensorType());
			
			//Holt die Timestampliste und nimmt den kleinsten und den größten Wert und subtrahiert diese
			first = sNode.getSensorList().get(i).getTimeStampList().get(0);
			last = sNode.getSensorList().get(i).getTimeStampList().get(sNode.getSensorList().get(i).getTimeStampList().size()-1);
			
			long subtrahent = last-first;
			if((subtrahent < difference) || (difference == 0)){
				difference = subtrahent;
			}
								
								//Test-Output
								System.out.println("First: "+  first + " Last: " + last);
								System.out.println("Last-First = " + subtrahent);
								
	}

								System.out.println("Subtrahent: "+ difference);
			
								
		
								//Test-Input
//								String firstDate = "01.01.2012";
//								String firstTime = "01:00:00";
//								String lastDate = "21.01.2012";
//								String lastTime = "01:00:00";
		
		
//		long first = timeInMilis(firstDate, firstTime);
//		long last = timeInMilis(lastDate, lastTime);
		
								//Test-Output
								//System.out.println("First: " + firstLong + " Last: " + lastLong);
								//System.out.println("Difference: " + difference);
				
		//Ermittelt das Minimum - An welchem Wert die Zeitspanne des Sensors am dichtesten dran ist
								
//								//Zur Überprüfung des Minimums
//								min = Math.min(Math.abs(difference - referenceValue[0]), Math.min(Math.abs(difference - referenceValue[1]), Math.min(Math.abs(difference - referenceValue[2]), Math.min(Math.abs(difference - referenceValue[3]), Math.min(Math.abs(difference - referenceValue[4]), Math.abs(difference - referenceValue[5])))))); 
//		
//								
//
//								
//								//Test-Output
//								//System.out.println("MIN = " + min);
//		
//		//Speichert die Berechneten Betragswerte in das array						
//		for(int i=0; i < referenceValue.length; i++){
//			absoluteValue[i] = Math.abs(difference - referenceValue[i]);
//								//Test-Output
////								System.out.println(absoluteValue[i]);
//		}
//			
//		//Ermittelt das Minimum und den dazugehörigen TW Vergleichswert
//		long minimum = absoluteValue[0];
//		fitValue = referenceValue[0];
//								
//
//		for (int i =0; i < absoluteValue.length; i++) {
//	        if (absoluteValue[i] < minimum){
//	            minimum = absoluteValue[i];
//	        	fitValue = referenceValue[i];
//	        }					
//	    }
//		
//														
//		//Wählt das Vergleichsweise beste TimeWindow aus
//		for(int i=0; i < referenceValue.length; i++){
//			if(fitValue == referenceValue[i]){
//				bestTimeWindow = timeWindowValues[i];
//			}
//		}
//								//Test-Output
//								System.out.println("Minimum: " + minimum + " ReferenceValue: " + fitValue + " BestTimeWindow: " + bestTimeWindow);
//								
	
	return catchBestTimeWindowFromReference(difference);
	
	}
	
	

	public static long calculateCommonTimeWindow(long currentCommonTimeWindow, long timeWindow){
		long newTimeWindow = 0;
		
		//Wenn das neue TW kleiner ist als das aktuelle TW dann wird das neue, kleinere verwendet  
		if(timeWindow < currentCommonTimeWindow){
			newTimeWindow = timeWindow;
		}
		else { //Wenn das aktuelle kleiner ist als das neue, bleibt das aktuelle bestehen
			newTimeWindow = currentCommonTimeWindow;
		}
		
		return newTimeWindow;
	}
	
	
	/**
	 * Sucht im Datensatz ein wiederholendes Muster (Periode) um an Hand dieser dann das Tw
	 * zu bestimmen.
	 * @param sNode
	 * @return
	 */
	public static long calculateCumputedTimeWindow(KSensorNode sNode){
		long[] timeWindow = new long[sNode.getSensorList().size()];
		long newTimeWindow = 0;
		double toleranceValue = 0.05D; 
		List<Double> dataList;
		List<Long> timestampList;
		double value;
		int howManyPeriods =0;
		int gradingFactor =3;    //only uneven numbers - nur ungerade Zahlen zb. 1: keine Glättung, 3, 5, 7...
		
		System.out.println("Start Computing");
		System.out.println(sNode.getDescription() + " Anzahl Sensoren: " + sNode.getSensorList().size());
		
		//Geht den ganzen Algorithmus für den Datensatz jedes Sensors durch
		for(int i=0; i < sNode.getSensorList().size(); i++){
		
			KSensor sensor = sNode.getSensorList().get(i);
			dataList = sensor.getDataList();
			timestampList = sensor.getTimeStampList();
			value = dataList.get(0);
							

									//Test-Output
									System.out.println("SensorElement1 " + value);
			
			//Initialisiert perioden Array an allen Stellen mit 0
			int periode[] = new int[dataList.size()];
				for(int g=0; g < periode.length; g++){
					periode[g] = 0;
				}
			System.out.println(dataList.size());
				
			
			/**
			 * Glättung der Werte:
			 * Dient dem Ausbügeln kleinerer Ungenauigkeite in den Sensorwerte um später besser nach einer Periode suchen zu können
			 * wie z.B. "...10,11,10..." --> wird zu "...10,10,10..."
			 * Der Glättungsfaktor gibt an wie viele Werte vor und nach dem aktuell betrachtetem Wert berücksichtigt werden sollen
			 * Je größer dieser ist desto "glatter" wird die Funktion.
			 * 
			 * Vorgehensweise:
			 * (glaettungsFaktor-1)/2 : startet die Glättung so in Abhängigkeit vom glaettungsFaktor dass links und rechts immer genügend Werte
			 * vorhanden sind zum glätten.
			 * Die Anzahl der bei der Glättung zu berücksichtigen Werte wird eingelesen (Aktueller Wert + nötigen Werte vor und nach diesem Wert)
			 * Dann werden diese Werte der größe nach aufsteigend sortiert und der Wert der dann in der Mitte steht, wird dann 
			 * an die STelle des in der dataList stehendem Wert geschrieben	
			 */
			double[] gradingValues = new double[gradingFactor];
			int vorUndNach = (int)Math.round((gradingFactor-1)/2);		//Bestimmen wieviele Felder vor und nach dem aktuellen Feld mit eingeschlossen werden						
			
			System.out.println("Start gradingProcess");
			
			for(int k = vorUndNach; k < (dataList.size()-vorUndNach); k++){			//geht alle Werte von 0 bis dataList.size() durch
				
				//Werte auslesen:
				int inc=0;
				int subAndAddValue =1;
				
				gradingValues[gradingFactor-1] = dataList.get(k); //Aktueller Wert an die letzte Stelle des arrays
				while(subAndAddValue <= vorUndNach){			 				  //Nimmt immer so viele Werte vor und Nach dem aktuellen Wert wie durch glaettungsFaktor vorgegeben
					gradingValues[inc] = dataList.get(k-subAndAddValue);   //damit alle Stellen im array gefüllt werden
					inc++;
					gradingValues[inc] = dataList.get(k+subAndAddValue);   //prinzipiell ist die Reihenfolge egal da eh sortiert wird
					inc++;
					subAndAddValue++;
				}
				
				//Werte sortieren:
				double help;
				boolean unsorted=true;
				
				while(unsorted){
					unsorted = false;
					for(int g=gradingValues.length-1; g > 0 ; g--){
						
						if(gradingValues[g] < gradingValues[g-1]){
							help = gradingValues[g-1];
							gradingValues[g-1] = gradingValues[g];
							gradingValues[g] = help;
							unsorted =true;
						}
					}
				}
				//Mittleren Wert rausnehmen:
				double midValue = gradingValues[vorUndNach];
				
				//Aktuellen Wert aus der Liste überschreiben
				dataList.set(k, midValue);
									
			}
											//Test-Output
											System.out.println("New DataList:");
											for(int g=0; g < dataList.size(); g++)
											System.out.println(dataList.get(g));
			
			
			/**
			 * Perioden suchen:
			 * 
			 */
			int j =1;
			boolean down = false;	
			//Datenliste durchlaufen und Werte vergleichen
			while(j < dataList.size()){
				//Wenn nächster Wert größer ist und man noch bei steigenden Werten ist
				if(j < dataList.size()){
					if( ((value < dataList.get(j)) || (value == dataList.get(j)) ) && (down == false)){
						value = dataList.get(j);
						j++;
					}
				}
				//Wenn nächster Wert kleiner ist Beginn der fallenden Werte
				if(j < dataList.size()){
					if((value > dataList.get(j))){
						value = dataList.get(j);
						down = true;
						j++;
					}
				}
				//Wenn man bei den fallenden Werten ist und die nachfolgende Zahl gleich ist
				if(j < dataList.size()){
					if((value == dataList.get(j)) && (down == true)){
						value = dataList.get(j);
						j++;
					}
				}
				//Wenn man bei den fallenden Werte ist und die nachfolgende Zahl größer ist --> wechsel in die Phase der steigenden Werte
				if(j < dataList.size()){
					if( ((value < dataList.get(j)) ) && (down == true)){
						down = false;
						periode[j]=1;
						howManyPeriods++;
						System.out.println("Periode on Position: " + j + " detected.");					
					}
				}
				
			}
		
		/***
		* Timestamps passend zu den erkannten Perioden erfassen
		* 
		*/
			long untilTimestamp = 0;
			long fromTimestamp = timestampList.get(0);
			long[] calculatedTimestamp = new long[howManyPeriods];
			int incPeriods = 0;
			
			for(int g=0; g < periode.length; g++){ 
				if(periode[g] == 1){
					untilTimestamp = timestampList.get(g); 		//Passenden Timestamp holen wo die Periode endet
					calculatedTimestamp[incPeriods] = untilTimestamp - fromTimestamp;
										System.out.println(untilTimestamp + " - " + timestampList.get(0) + " = " + calculatedTimestamp[incPeriods]);
					fromTimestamp = timestampList.get(g+1);		//From ist als nächstes der wert nach untilTimestamp (Setzt neuen periodenbeginn)
					incPeriods++;
				}
			}
			
			//Mittelwert bilden über alle gefunden Perioden (bzw. über die Zeitspanne (Timestamps) der Perioden):
			long sum = 0;
			long arithmeticAverage = 0; 
			
			for(int g=0; g < calculatedTimestamp.length; g++){		//Summe bilden
				sum = sum + calculatedTimestamp[g]; 
			}
			
			arithmeticAverage = (long)Math.round(sum / howManyPeriods);
			timeWindow[i] = arithmeticAverage;						//Speichert den ermittelten Timestampwert, der für die Annäherung an ein passendes TimeWindow
		}															//verwendet wird in der globaleren Variable ab
		
		
		/***
		 * Die von allen im Knoten vorhandenen Sensoren gebildeten Timestampwerte (für Annäherung an ein passendes TW)  
		 * werden verglichen und das kleinste von ihnen wird genommen um ein passendes TW aus den Vorgaben zu wählen
		 * 
		 */
		//der Größe nach sortieren
		long help;
		boolean unsorted=true;
		
		while(unsorted){
			unsorted = false;
			for(int g=timeWindow.length-1; g > 0 ; g--){
				
				if(timeWindow[g] < timeWindow[g-1]){
					help = timeWindow[g-1];
					timeWindow[g-1] = timeWindow[g];
					timeWindow[g] = help;
					unsorted =true;
				}
			}
		}
		
					//Test-Outputs
					for(int l=0; l < timeWindow.length; l++)
					System.out.println("TimeWindow = " + timeWindow[l]);
		
		
		//der kleinste wert steht jetzt an erster stelle [0]
			
		//mit den TimeWindow Vorgaben abgleichen und das passende TimeWindow wählen
		//Ermittelt das Minimum und den dazugehörigen TW Vergleichswert
		newTimeWindow = catchBestTimeWindowFromReference(timeWindow[0]);
		
					//Test-Output
					System.out.println("NewTimeWindow: " + newTimeWindow);
		
		return newTimeWindow;
	}
	
	
	
	
	/**
	 * Ermittelt das Beste TimeWindow in Annäherung zu den ReferenceTimeWindows
	 * @return
	 */
	
	public static long catchBestTimeWindowFromReference(long difference){
	
		//Zur Überprüfung des Minimums
		min = Math.min(Math.abs(difference - referenceValue[0]), Math.min(Math.abs(difference - referenceValue[1]), Math.min(Math.abs(difference - referenceValue[2]), Math.min(Math.abs(difference - referenceValue[3]), Math.min(Math.abs(difference - referenceValue[4]), Math.abs(difference - referenceValue[5])))))); 

		//Speichert die Berechneten Betragswerte in das array						
		for(int i=0; i < referenceValue.length; i++){
			absoluteValue[i] = Math.abs(difference - referenceValue[i]);
		}
		
		//Ermittelt das Minimum und den dazugehörigen TW Vergleichswert
		long minimum = absoluteValue[0];
		fitValue = referenceValue[0];
				
		
		for (int i =0; i < absoluteValue.length; i++) {
			if (absoluteValue[i] < minimum){
				minimum = absoluteValue[i];
				fitValue = referenceValue[i];
			}					
		}
		
										
		//Wählt das Vergleichsweise beste TimeWindow aus
		for(int i=0; i < referenceValue.length; i++){
			if(fitValue == referenceValue[i]){
				bestTimeWindow = timeWindowValues[i];
			}
		}
				//Test-Output
				System.out.println("Minimum: " + minimum + " ReferenceValue: " + fitValue + " BestTimeWindow: " + bestTimeWindow);
		
	return bestTimeWindow;
		
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	//Wandelt die Datum + Zeit in ein Timestamp Format long um
	public static long timeInMilis(String date, String time) {
			TString sd = new TString(date, '.');
			TString st = new TString(time, ':');
			Calendar c = Calendar.getInstance();
			int day = Integer.valueOf(sd.getStrAt(0));
			int month = Integer.valueOf(sd.getStrAt(1))-1;
			int year = Integer.valueOf(sd.getStrAt(2));
			int h = Integer.valueOf(st.getStrAt(0));
			int m = Integer.valueOf(st.getStrAt(1));
			int s = Integer.valueOf(st.getStrAt(2));
			c.set(year, month, day, h, m, s);
			
	    return c.getTimeInMillis();
	}
	
	
	//Wandelt Timestamp im aus Format long in das normale Datum + Zeit um
	public static String timeOf(long t) {
//			Calendar c = Calendar.getInstance();
//			c.setTimeInMillis(t);
			SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyy HH:mm:ss");
			df.setTimeZone( TimeZone.getDefault() ); 
			return df.format(t);
			
		}
		
	}



